/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.skimmed;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Denotes a Jarp in the loadlist.
 * 
 * Note that each configuration has a seperate Jarp object.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public class Jarp implements Serializable, Cloneable{
    
    private static final long serialVersionUID = -8939321012161976204L;
    
    /**
     * Denotes a requirement for a Jarp.
     * @author Thomas Kerber
     * @version 1.0.0
     */
    public class Req implements Serializable, Cloneable{
        
        private static final long serialVersionUID = 9094849359138128466L;
        
        /**
         * The requirement name (regex to match).
         */
        private String reqName;
        
        /**
         * The resolved requirement Jarp. Null means the requirement couldn't
         * be resolved.
         */
        public transient Jarp req = null;
        
        /**
         * THIS METHOD IS FOR DESERIALIZATION ONLY!
         */
        @Deprecated
        public Req(){} // For Serialization ONLY!
        
        /**
         * 
         * @param name The requirement name (regex to match).
         */
        public Req(String name){
            reqName = name;
        }
        
        /**
         * 
         * @return The requirement name (regex).
         */
        public String getReqName(){
            return reqName;
        }
        
        /**
         * 
         * @param name The name to match against.
         * @return Whether or not this name counts as fulfilling the
         *     requirement.
         */
        private boolean matchesName(String name){
            // It is assumed that name is a valid regex.
            return reqName.matches(".*" + name + ".*");
        }
        
        /**
         * Checks if requirement is met.
         * 
         * Note: c.initReqs() must have been called previously.
         * @param c The configuration to check.
         * @return Whether or not the requirement is met.
         */
        public boolean reqMet(Configuration c){
            // TODO: come up with a system to note missing reqs.
            if(req == null)
                return false;
            boolean reqLoaded = c.getLoadStatus(req).isLoaded();
            boolean reqBefore = c.getLoadList().indexOf(req) <
                    c.getLoadList().indexOf(Jarp.this);
            return reqLoaded && reqBefore;
        }
        
        /**
         * Resolves the req to a Jarp.
         * @param c The configuration to resolve with.
         * @return Whether or not it could be resolved.
         */
        public boolean assignReq(Configuration c){
            for(Jarp j : c.getLoadList()){
                if(matchesName(j.getName())){
                    req = j;
                    return true;
                }
            }
            req = null;
            return false;
        }
        
        @Override
        public Req clone(){
            Object o = null;
            try{
                o = super.clone();
            }
            // Squishy, squishy XD.
            catch(CloneNotSupportedException e){}
            Req r = (Req)o;
            r.req = null;
            return r;
        }
        
        @Override
        public boolean equals(Object o){
            if(o == null)
                return false;
            if(!(o instanceof Req))
                return false;
            return reqName.equals(((Req)o).reqName);
        }
        
    }
    
    /**
     * The requirements of this Jarp.
     */
    private Req[] reqs;
    /**
     * The location of this Jarp.
     */
    public File location;
    /**
     * This jarps config.
     */
    public transient Properties conf;
    /**
     * This jarps name.
     */
    private String name;
    /**
     * This jarps on-screen name.
     */
    private String displayName;
    /**
     * This jarps description.
     */
    private String desc = null;
    
    /**
     * 
     * @return This jarps requirements.
     */
    public Req[] getReqs(){
        return reqs;
    }
    
    /**
     * 
     * @return This jarps name.
     */
    public String getName(){
        return name;
    }
    
    /**
     * 
     * @return This jarps on-screen name.
     */
    public String getDisplayName(){
        return displayName;
    }
    
    /**
     * 
     * @return This jarps description. (Or null if none exists)
     */
    public String getDesc(){
        return desc;
    }
    
    /**
     * 
     * @param c The active configuration.
     * @return Whether or not the requirements or this jarp have been met in
     *     the configuration.
     */
    public boolean reqsMet(Configuration c){
        for(Req r : reqs){
            if(!r.reqMet(c))
                return false;
        }
        return true;
    }
    
    /**
     * FOR DESERIALIZATION ONLY!
     */
    @Deprecated
    public Jarp(){}
    
    /**
     * 
     * @param location The jarps location.
     * @throws IOException
     */
    public Jarp(File location) throws IOException{
        this.name = location.getName();
        this.location = location;
        this.displayName = this.name;
        // Reqs are to be contained in the JARPS milk.properties.
        ZipFile jarp = new ZipFile(location);
        ZipEntry conf = jarp.getEntry("milk.properties");
        if(conf != null){
            InputStream in = jarp.getInputStream(conf);
            this.conf = new Properties();
            this.conf.load(in);
            in.close();
            if(this.conf.contains("reqs")){
                String reqStr = this.conf.getProperty("reqs");
                String[] reqStrs = reqStr.split(",\\s*");
                reqs = new Req[reqStrs.length];
                for(int i = 0; i < reqStrs.length; i++)
                    reqs[i] = new Req(reqStrs[i].
                            replaceAll("\\\\({, })", "$1"));
            }
            if(this.conf.contains("desc")){
                this.desc = this.conf.getProperty("desc");
            }
            if(this.conf.contains("name")){
                this.displayName = this.conf.getProperty("name");
            }
        }
    }
    
    @Override
    public Jarp clone(){
        Object ret = null;
        try{
            ret = super.clone();
        }
        // Squishy, squishy!
        catch(CloneNotSupportedException e){}
        return (Jarp)ret;
    }
    
    @Override
    public boolean equals(Object o){
        if(o == null)
            return false;
        if(!(o instanceof Jarp))
            return false;
        if(!name.equals(((Jarp)o).name))
            return false;
        if(reqs.length != ((Jarp)o).reqs.length)
            return false;
        for(int i = 0; i < reqs.length; i++)
            if(!reqs[i].equals(((Jarp)o).reqs[i]))
                return false;
        return true;
    }
    
}
